#include <Precomp.h>

#pragma warning(disable:4201)

#include <MMSystem.h>

#include "defs.h"
#include "Application.h"
#include "ServerConnection.h"
#include "ClientListener.h"
#include "UIManager.h"
#include "BaseGameState.h"
#include "GameStateInGame.h"
#include "packet.h"
#include "GameWorld.h"
#include "Player.h"
#include "InputManager.h"
#include "GamePlayerList.h"
#include "GameHud.h"
#include "ChatList.h"
#include "UserInput.h"
#include "DetailViewWindow.h"
#include "GameSoundManager.h"
#include "GameShipExplosionManager.h"
#include "Radar.h"


#include "AVUIDispatcher.h"
#include "AVUIInputManager.h"
#include "AVUINavigationWindow.h"
#include "AVUITransitioningPage.h"
#include "AVUIGeneralTransform.h"

using namespace AVUI;

using namespace Ogre;

const float CGameStateInGame::c_flDefaultCameraDistance = 4000.0f;
const float CGameStateInGame::c_flMaxCameraDistance = 8000.0f;
const float CGameStateInGame::c_flMinCameraDistance = 500.0f;


void CGameStateInGame::Initialize(CNetrekClientListener* pListener)
{

    CBaseGameState::Initialize(pListener);

    m_eCameraType = eCameraType_ThreeQuarter;

    FrameworkElement* pFE = CUIManager::LoadXamlScene(L"Media\\Xaml\\InGame.xaml");
    m_pRoot = pFE;                                                      

    ((NavigationWindow*)Application::get_Current()->get_MainWindow())->Navigate(m_pRoot);

    if(m_pRoot != NULL)
    {
        RoutedEventArgs eventArgs(TransitioningPage::get_TransitionInEvent());
        m_pRoot->RaiseEvent(&eventArgs);
    }

    pFE->Release();
    pFE = NULL;

    m_pMainPlayfield = (FrameworkElement*)m_pRoot->FindName(object_allocate<AVUI::String>(L"MainPlayfield"));

    m_pNewRadar = (Radar*)m_pRoot->FindName(object_allocate<AVUI::String>(L"Radar"));
    m_pNewRadar->set_GameWorld(pListener->GetGameWorld());
    m_pNewRadar->set_ClientListener(pListener);

    DetailViewWindow* pDetailWindow = (DetailViewWindow*)m_pRoot->FindName(object_allocate<AVUI::String>(L"DetailViewWindow"));
    pDetailWindow->set_GameWorld(pListener->GetGameWorld());

    m_pDetailViewWindow = pDetailWindow;

    m_pListener->GetSceneManager()->setAmbientLight(ColourValue(0.5, 0.5, 0.5));

    m_pCamera = m_pListener->GetSceneManager()->createCamera("InGameCamera");

    m_pViewport = pListener->GetApp()->GetRenderWindow()->addViewport(m_pCamera, 0, 0.0f, 0.05f, 0.70f, 0.9f);

    m_pViewport->setOverlaysEnabled(false);
    m_pViewport->setBackgroundColour(ColourValue(0,0,0));


    // Alter the camera aspect ratio to match the viewport
    m_pCamera->setAspectRatio(
        Real(m_pViewport->getActualWidth()) / Real(m_pViewport->getActualHeight()));

    m_pPlayerList = new CGamePlayerList(pListener->GetGameWorld(), (ListView*)m_pRoot->FindName(object_allocate<AVUI::String>("PlayerList")));

    m_pChatList = new CChatList(pListener->GetGameWorld(), (ListBox*)m_pRoot->FindName(object_allocate<AVUI::String>("ChatHistory")), (TextBox*)m_pRoot->FindName(object_allocate<AVUI::String>("ChatEntry")));

    m_pGameHud = new CGameHud(pListener->GetGameWorld(), m_pRoot);

    m_pUserInput = new CUserInput(pListener->GetServerConnection(), pListener->GetGameWorld(), pListener->GetInputManager());

    m_pGameSoundManager = new CGameSoundManager(pListener->GetGameWorld(), pListener->GetApp()->GetSoundManager());

    m_pGameShipExplosionManager = new CGameShipExplosionManager(pListener->GetGameWorld());
                                               
    m_fWasAlive = false;
    m_flTimeTillNextRequest = 0.0f;
    m_flCameraDistance = c_flDefaultCameraDistance;

}

void CGameStateInGame::UpdateCamera()
{
    CLocalPlayer* pLocalPlayer = m_pListener->GetGameWorld()->GetLocalPlayer();
    CGamePlayer* pGamePlayer = m_pListener->GetGameWorld()->GetPlayerByID(pLocalPlayer->GetPlayerID());


    if(m_eCameraType == eCameraType_ThreeQuarter)
    {
        float flBack = m_flCameraDistance;
        m_pCamera->setPosition(Vector3(pGamePlayer->GetRenderedXLocation(), pGamePlayer->GetRenderedYLocation() + flBack , -1.73f * flBack));

        // Look back along -Z
        m_pCamera->lookAt(Vector3(pGamePlayer->GetRenderedXLocation(),pGamePlayer->GetRenderedYLocation() + flBack / 10.0f, 0.0f));
        m_pCamera->roll(Radian(Math::PI));
    }
    else
    {
        float flBack = m_flCameraDistance;
        m_pCamera->setPosition(Vector3(pGamePlayer->GetRenderedXLocation(), pGamePlayer->GetRenderedYLocation(), -2.25f * flBack));

        // Look back along -Z
        m_pCamera->lookAt(Vector3(pGamePlayer->GetRenderedXLocation(),pGamePlayer->GetRenderedYLocation(), 0.0f));
        m_pCamera->roll(Radian(Math::PI));
    }
}

#include <Windows.h>

DWORD dwNextTime = 0;

void CGameStateInGame::FrameStart()
{
//    if(timeGetTime() > dwNextTime)
    {
        m_pListener->GetServerConnection()->UpdateWithPacketSink(this);

        dwNextTime = timeGetTime() + 75;
    }
    m_pListener->GetGameWorld()->FrameUpdate();

    if(m_pGameHud != NULL)
    {
        m_pGameHud->FrameUpdate();
    }

    if(m_pGameSoundManager != NULL)
    {
        m_pGameSoundManager->Update();
    }

    if(m_pGameShipExplosionManager != NULL)
    {
        m_pGameShipExplosionManager->Update();
    }

    UpdateCamera();


    EPlayerStatus eLocalPlayerStatus = m_pListener->GetGameWorld()->GetPlayerForLocalPlayer()->GetPlayerStatus();

    if(m_fWasAlive && eLocalPlayerStatus != ePlayerStatus_Alive)
    {
        if(m_flTimeTillNextRequest == 0.0f)
        {
            CGamePlayer* pPlayer = m_pListener->GetGameWorld()->GetPlayerForLocalPlayer();

            m_pListener->GetServerConnection()->SendTeamRequest(pPlayer->GetTeamID(), pPlayer->GetShipData()->GetShipType());
            m_flTimeTillNextRequest = 1.0f;
        }
        else
        {
            m_flTimeTillNextRequest -= m_pListener->GetTimeSinceLastFrameAsFloat();
            if(m_flTimeTillNextRequest < 0.0f)
            {
                m_flTimeTillNextRequest = 0.0f;
            }
        }
    }

    m_pMainPlayfield->InvalidateRect(m_pMainPlayfield->get_RenderSize());
    m_pDetailViewWindow->InvalidateRect(m_pDetailViewWindow->get_RenderSize());

    if(Application::get_Current()->get_MainWindow()->IsAncestorOf(m_pMainPlayfield))
    {
        AVUI::Rect globalRect = m_pMainPlayfield->TransformToAncestor(Application::get_Current()->get_MainWindow())->TransformBounds(AVUI::Rect(m_pMainPlayfield->get_RenderSize()));
        AVUI::Rect windowRect = Application::get_Current()->get_PresentationSourceSize();

        if(!globalRect.IsEqual(m_mainRect))
        {
            float flLeft = (globalRect.get_Left() + 1.0f) / (1.0f * windowRect.get_Width());
            float flTop = (globalRect.get_Top() + 1.0f) / (1.0f * windowRect.get_Height());
            float flWidth = (globalRect.get_Width()) / (1.0f * windowRect.get_Width());
            float flHeight = (globalRect.get_Height()) / (1.0f * windowRect.get_Height());

            m_pViewport->setDimensions(flLeft, flTop, flWidth, flHeight);

            m_pCamera->setAspectRatio(
                Ogre::Real(m_pViewport->getActualWidth()) / Ogre::Real(m_pViewport->getActualHeight()));

            m_mainRect = globalRect;
        }
    }
}

void CGameStateInGame::Cleanup()
{
    m_pListener->GetSceneManager()->destroyCamera(m_pCamera);
    m_pCamera = NULL;

    m_pListener->GetApp()->GetRenderWindow()->removeViewport(m_pViewport->getZOrder());
    m_pViewport = NULL;

    delete m_pGameShipExplosionManager;
    m_pGameShipExplosionManager = NULL;

    delete m_pGameSoundManager;
    m_pGameSoundManager = NULL;

    delete m_pPlayerList;
    m_pPlayerList = NULL;

    delete m_pChatList;
    m_pChatList = NULL;

    delete m_pGameHud;
    m_pGameHud = NULL;

    delete m_pUserInput;
    m_pUserInput = NULL;

    Application::get_Current()->get_MainWindow()->set_Content(NULL);

}

bool CGameStateInGame::ProcessPacket(EServerPacketType ePacketType, const packet_base* pPacket)
{
    m_pListener->GetGameWorld()->ProcessPacket(ePacketType, pPacket);

    EPlayerStatus eLocalPlayerStatus = m_pListener->GetGameWorld()->GetPlayerForLocalPlayer()->GetPlayerStatus();

    if(eLocalPlayerStatus == ePlayerStatus_Alive)
    {
        m_fWasAlive = true;
    }

    return true;
}

bool CGameStateInGame::GetMouseGridIntersection(Vector3& vectorResult)
{
    AVUI::Point mousePosition = Dispatcher::get_CurrentDispatcher()->get_InputManager()->get_MousePosition(NULL);
    float flAbsoluteX = mousePosition.get_X();
    float flAbsoluteY = mousePosition.get_Y();
    float flXPosition = (flAbsoluteX - m_pViewport->getActualLeft()) / m_pViewport->getActualWidth();
    float flYPosition = (flAbsoluteY - m_pViewport->getActualTop()) / m_pViewport->getActualHeight();

    if(flXPosition < 0.0f || flXPosition > 1.0f)
    {
        return false;
    }

    if(flYPosition < 0.0f || flYPosition > 1.0f)
    {
        return false;
    }

    Plane plane;

    plane.d = 0;
    plane.normal = -Vector3::UNIT_Z;

    Ray ray = m_pCamera->getCameraToViewportRay(flXPosition, flYPosition);

    std::pair<bool, Real> rayResult = ray.intersects(plane);

    if(!rayResult.first)
    {
        return false;
    }

    vectorResult = ray.getPoint(rayResult.second);

    return true;
}

void CGameStateInGame::MouseMoved(int, int, int zDiff)
{
    const float flScrollMultiplier = 3.0f;

    m_flCameraDistance += -zDiff * flScrollMultiplier;

    if(m_flCameraDistance > c_flMaxCameraDistance)
    {
        m_flCameraDistance = c_flMaxCameraDistance;
    }

    if(m_flCameraDistance < c_flMinCameraDistance)
    {
        m_flCameraDistance = c_flMinCameraDistance;
    }
}


void CGameStateInGame::MousePressed(EMouseButtonID eMouseButtonID)
{
    if(m_pViewport == NULL)
    {
        return;
    }
    // Vector from player location to click point
    Vector3 resultVector;

    if(!GetMouseGridIntersection(resultVector))
    {
        m_pUserInput->MousePressed(eMouseButtonID, CUserInputMouseHitData());
        return;
    }

    CGamePlayer* pGamePlayer = m_pListener->GetGameWorld()->GetPlayerForLocalPlayer();
    Vector3 rayVector = resultVector - Vector3(pGamePlayer->GetXLocation() * 1.0f, pGamePlayer->GetYLocation() * 1.0f, 0.0f);

    rayVector.normalise();
    // Dot product with negative y to get angle deviation from top dead center.
    float dotProduct = rayVector.dotProduct(Vector3(0.0f, -1.0f, 0.0f));

    float flAngle = acos(dotProduct);
    if(rayVector.x < 0.0f)
    {
        flAngle = (Math::TWO_PI) - flAngle;
    }

    BYTE bAngle = (BYTE) (256.0f * flAngle / (2.0f * 3.14f));

    m_pUserInput->MousePressed(eMouseButtonID, CUserInputMouseHitData((DWORD) resultVector.x, (DWORD)resultVector.y, bAngle));
}

void CGameStateInGame::KeyPressed(EKeyboardButtonID eKeyboardButtonID)
{
    if(m_pChatList != NULL && m_pChatList->HandleKeyPressed(eKeyboardButtonID))
    {
        return;
    }

    // Vector from player location to click point
    Vector3 resultVector;

    if(eKeyboardButtonID == VK_F1)
    {
        if(m_eCameraType == eCameraType_ThreeQuarter)
        {
            m_eCameraType = eCameraType_Overhead;
        }
        else
        {
            m_eCameraType = eCameraType_ThreeQuarter;
        }
    }

    if(!GetMouseGridIntersection(resultVector))
    {
        m_pUserInput->KeyPressed(eKeyboardButtonID, CUserInputMouseHitData());
        return;
    }

    CGamePlayer* pGamePlayer = m_pListener->GetGameWorld()->GetPlayerForLocalPlayer();
    Vector3 rayVector = resultVector - Vector3(pGamePlayer->GetXLocation() * 1.0f, pGamePlayer->GetYLocation() * 1.0f, 0.0f);
    rayVector.normalise();
    // Dot product with negative y to get angle deviation from top dead center.
    float dotProduct = rayVector.dotProduct(Vector3(0.0f, -1.0f, 0.0f));

    bool isNegative = false;
    if(rayVector.x < 0.0f)
    {
        isNegative = true;
    }

    float flAngle = acos(dotProduct);

    if(isNegative)
    {
        flAngle = (Math::TWO_PI) - flAngle;
    }

    BYTE bAngle = (BYTE) (256.0f * flAngle / (2.0f * 3.14f));

    m_pUserInput->KeyPressed(eKeyboardButtonID, CUserInputMouseHitData((DWORD) resultVector.x, (DWORD)resultVector.y, bAngle));
}

